package com.sunday.common.dubbo.tools;


import com.sunday.common.core.constants.LogConstants;
import com.sunday.common.core.constants.StringConstants;
import com.sunday.common.core.enums.ErrorCodeEnum;
import com.sunday.common.core.exception.CoreException;
import com.sunday.common.core.remoting.base.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;

/**
 * 生产者异常转换
 * PS：说一下为什么没有进行 consumer端的拦截原因， 因为大家都知道HTTP当异常返回的时候，我们大多数都是try catch 来进行捕获的，
 * 其中的异常分门别类是很多的，无法做到面面俱到，例如 dubbo ExceptionFilter 基本都是将封装的异常进行抛出，因为单是filter无法进行异常项的分析。
 * 而 provider 可以做是建立在我们系统中有完善的处理机制，可以保障业务自身发生异常时的处理，而不依赖外部的信息。
 * 这就是为什么consumer不能进行拦截，因为我们无法分辨不同情况下的异常引发的后续业务操作，人工介入可能才是最好的方法。
 * <p>
 * 参考已有实现
 * org.apache.dubbo.rpc.filter.CompatibleFilter
 * org.apache.dubbo.rpc.filter.ExceptionFilter
 * org.apache.dubbo.rpc.filter.ExecuteLimitFilter
 * <p>
 * <p>
 * https://dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/spi/description/filter/#%E5%B7%B2%E7%9F%A5%E6%89%A9%E5%B1%95
 * <p>
 * 调用拦截扩展
 * <p>
 * 扩展说明
 * 服务提供方和服务消费方调用过程拦截，Dubbo 本身的大多功能均基于此扩展点实现，每次远程方法执行，该拦截都会被执行，请注意对性能的影响。
 * <p>
 * 约定：
 * <p>
 * 用户自定义 filter 默认在内置 filter 之后。
 * 特殊值 default，表示缺省扩展点插入的位置。比如：filter="xxx,default,yyy"，表示 xxx 在缺省 filter 之前，yyy 在缺省 filter 之后。
 * 特殊符号 -，表示剔除。比如：filter="-foo1"，剔除添加缺省扩展点 foo1。比如：filter="-default"，剔除添加所有缺省扩展点。
 * provider 和 service 同时配置的 filter 时，累加所有 filter，而不是覆盖。比如：<dubbo:provider filter="xxx,yyy"/> 和 <dubbo:service filter="aaa,bbb" />，则 xxx,yyy,aaa,bbb 均会生效。如果要覆盖，需配置：<dubbo:service filter="-xxx,-yyy,aaa,bbb" />
 * 扩展接口
 * org.apache.dubbo.rpc.Filter
 * <p>
 * org.apache.dubbo.rpc.filter.CompatibleFilter             使远程方法的返回值与调用者版本的对象兼容
 * org.apache.dubbo.rpc.filter.TpsLimitFilter               [PROVIDER]限制一个服务或一个特定方法的所有方法的TPS(每秒事务)
 * org.apache.dubbo.rpc.filter.TimeoutFilter                [PROVIDER] 记录任何调用超时，但不要停止服务器的运行
 * org.apache.dubbo.rpc.filter.EchoFilter                   [PROVIDER] Dubbo提供默认的Echo Echo服务，该服务可用于所有Dubbo提供程序服务接口
 * org.apache.dubbo.rpc.filter.GenericFilter                [PROVIDER] 通用调用程序过滤器
 * org.apache.dubbo.rpc.filter.GenericImplFilter            [PROVIDER] 通用调用程序过滤器
 * org.apache.dubbo.rpc.filter.TokenFilter                  [PROVIDER] 检查给定的提供者令牌是否与远程令牌匹配。如果不匹配, 不允许调用远程方法。
 * org.apache.dubbo.rpc.filter.AccessLogFilter              [PROVIDER] 记录服务的访问日志
 * org.apache.dubbo.rpc.filter.CountFilter                  未找到
 * org.apache.dubbo.rpc.filter.ActiveLimitFilter            [CONSUMER] 限制从客户端对服务或服务方法的并发客户端调用
 * org.apache.dubbo.rpc.filter.ClassLoaderFilter            [PROVIDER] 将当前执行线程类装入器设置为服务接口的类装入器
 * org.apache.dubbo.rpc.filter.ContextFilter                [PROVIDER] 为提供程序RpcContext设置调用程序、调用程序、使用的本地端口和主机, 当前执行线程
 * org.apache.dubbo.rpc.filter.ConsumerContextFilter        [CONSUMER] 设置当前RpcContext与调用程序，调用，本地主机，远程主机和端口, 用于消费者调用程序。它这样做是为了使require信息对执行线程的RpcContext可用。
 * org.apache.dubbo.rpc.filter.ExceptionFilter              [PROVIDER] 意外异常将记录在提供者端的ERROR级别。未检查意外异常, 接口上没有声明异常,将API包中没有引入的异常包装到RuntimeException中。框架将序列化外部异常，但字符串化其原因，以避免客户端可能出现的序列化问题
 * org.apache.dubbo.rpc.filter.ExecuteLimitFilter           [PROVIDER] 提供程序的每个方法每个服务的最大并行执行请求计数。如果最大配置, 执行设置为10，如果调用请求已经是10，则会抛出异常。它继续同样的行为，直到它是
 * org.apache.dubbo.rpc.filter.DeprecatedFilter             [CONSUMER] 记录错误消息，如果调用的方法已被标记为deprecated。检查是否有方法
 * <p>
 * <p>
 * result.setException(null);
 * 此处不清空就会被主动抛出异常
 * 例如: org.apache.dubbo.rpc.AppResponse#recreate()
 * public Object recreate() throws Throwable {
 * if (exception != null) {
 * throw exception;
 * }
 * }
 *
 * @author zsy
 * @since 2024/1/18
 */
@Slf4j
@Activate(group = {CommonConstants.PROVIDER})
public class ThrowableConvertResponseFilter implements Filter {

    private static final String methodName = "resultReturn";
    private static final Method method;

    static {
        method = ReflectionUtils.findMethod(Response.class, methodName, String.class, String.class);
        method.setAccessible(true);
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result = null;
        try {
            result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                Throwable exception = result.getException();
                result.setValue(returnResponse(exception));
                result.setException(null);
            }
        } catch (Throwable ex) {
            if (result == null) {
                result = AsyncRpcResult.newDefaultAsyncResult(returnResponse(ex), invocation);
            } else {
                result.setValue(returnResponse(ex));
                result.setException(null);
            }
        }
        return result;
    }

    public static <T> Response<T> returnResponse(Throwable cause) {
        // C0_110(ErrorClassifyEnum.C + "0110","RPC 服务出错"),
        Response response = Response.error(ErrorCodeEnum.C0_110);
        if (cause instanceof CoreException exception) {
            response = Response.class.cast(ReflectionUtils.invokeMethod(method, response, exception.getCode(), exception.getMessage()));
            log.info(LogConstants.response_format_throwable, response, StringConstants.EMPTY);
            return response;
        }
        log.error(LogConstants.response_format_throwable, response, cause.toString(), cause);
        return response;
    }

}
